package com.zy.explore.client.infrastructure.result;

import com.zy.explore.client.infrastructure.exception.MessageCode;
import com.zy.explore.client.infrastructure.exception.RemoteException;
import com.zy.explore.client.interfaces.vo.ResultVO;
import feign.FeignException;
import feign.Response;
import feign.codec.DecodeException;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * ResultVO 自动【解包】
 * feign 声明返回对象，调用时不需要再手动调用 getData()
 * 如果返回 status != 200 会包装为 {@link RemoteException} 异常抛出，可以在本地捕获
 *
 * 注意：
 * 本解包器仅支持配合【{@link ResultVOAdvice} 包装器】一起使用
 * 其他非 {@link ResultVO} 的返回值不支持
 *
 * @author administrator
 * @see RemoteException
 * @see ResultVOAdvice
 * @see ResultVO
 * @since 2020/8/27
 */
@Component
@ConditionalOnProperty(value = ResultVOConfigurer.ENABLED, matchIfMissing = true)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
class ResultVODecoder extends SpringDecoder {

    public ResultVODecoder(ObjectFactory<HttpMessageConverters> messageConverters) {
        super(messageConverters);
    }

    @Override
    @Nullable
    public Object decode(Response response, Type type) throws IOException, FeignException {
        if (isResultVO(type)) {
            return super.decode(response, type);
        }

        ParameterizedType wrappedResultVO = new ResultVOWrapper(type);
        ResultVO<?> result = (ResultVO<?>) super.decode(response, wrappedResultVO);

        if (result.getStatus() == null) {
            throw new DecodeException(response.status(), "远程调用异常，不支持反序列化对象类型，需要 ResultVO<T>，当前为 " + type, response.request());
        }

        if (result.getStatus() == MessageCode.OK.getCode()) {
            return result.getData();
        }

        throw new RemoteException(result.getStatus(), result.getMessage());
    }

    private boolean isResultVO(Type type) {
        if (!(type instanceof ParameterizedType)) {
            return false;
        }

        Type rawType = ((ParameterizedType) type).getRawType();
        return ResultVO.class.equals(rawType);
    }

    @Getter
    @EqualsAndHashCode
    private static class ResultVOWrapper implements ParameterizedType {

        private final Type[] actualTypeArguments;
        private final Class<?> rawType = ResultVO.class;
        private final Type ownerType = ResultVO.class.getEnclosingClass();

        public ResultVOWrapper(Type type) {
            this.actualTypeArguments = new Type[]{type};
        }
    }
}
